package org.apache.ibatis.common.io;

import org.apache.ibatis.common.logging.Log;
import org.apache.ibatis.common.logging.LogFactory;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @Name ResolverUtil
 * @Description: 类筛选工具类
 * 描述信息：该类主要完成目标类的筛选，筛选条件可以是：
 * - 类是否为某个接口或类的子类；
 * - 类是否具有某个注解。
 * 此外，该类还支持筛选出指定路径下的符合指定条件的类文件。
 * @Version v1.0.0
 * @Author: lichao
 * @Date: 2024-05-14 18:39:01
 **/
public class ResolverUtil<T> {
    private static final Log log = LogFactory.getLog(ResolverUtil.class);
    private Set<Class<? extends T>> matches = new HashSet<>();
    private ClassLoader classLoader;

    public ResolverUtil<T> findImplementations(Class<?> parent, String... packageNames) {
        if (packageNames == null) return this;
        Test test = new IsA(parent);
        for (String pkg : packageNames) find(test, pkg);
        return this;
    }

    public ResolverUtil<T> findAnnotated(Class<? extends Annotation> annotation, String... packageNames) {
        if (packageNames == null) return this;
        Test test = new AnnotatedWith(annotation);
        for (String pkg : packageNames) find(test, pkg);
        return this;
    }

    /**
     * 筛选出指定包路径下符合一定条件的类。
     */
    public ResolverUtil<T> find(Test test, String pkg) {
        // 获取起始包的路径
        String path = getPackagePath(pkg);
        try {
            // 找出包中的各个文件
            List<String> children = VFS.getInstance().list(path);
            for (String child : children) {
                // 对类文件进行测试，如果满足测试条件，则将该类文件记录下来
                if (child.endsWith(".class")) addIfMatching(test, child);
            }
        } catch (IOException e) {
            log.error("无法读取包：" + pkg, e);
        }
        return this;
    }

    protected String getPackagePath(String pkg) {
        return pkg == null ? null : pkg.replace('.', '/');
    }

    /**
     * 判断一个类文件是否满足测试条件，如果满足，则记录下来
     *
     * @param test 测试条件
     * @param fqn  类全限定名称
     */
    protected void addIfMatching(Test test, String fqn) {
        try {
            // 转化为外部名称
            String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
            ClassLoader classLoader = getClassLoader();
            if (log.isDebugEnabled()) {
                String msg = "检查类 [" + externalName + "] 是否符合测试条件 [" + test + "]";
                log.debug(msg);
            }
            // 加载类文件
            Class<?> type = classLoader.loadClass(externalName);
            // 执行测试
            if (test.matches(type)) {
                // 如果测试通过，则记录到 matches 属性中
                matches.add((Class<T>) type);
            }
        } catch (Throwable e) {
            String msg = "无法检查类 [" + fqn + "]，由于一个异常 {" + e.getClass().getName() + " -> " + e.getMessage() + "}";
            log.warn(msg);
        }
    }

    public Set<Class<? extends T>> getClasses() {
        return matches;
    }

    public ClassLoader getClassLoader() {
        return classLoader == null ? Thread.currentThread().getContextClassLoader() : classLoader;
    }

    public void setClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    public interface Test {
        boolean matches(Class<?> type);
    }

    public static class IsA implements Test {
        private Class<?> parent;

        public IsA(Class<?> parentType) {
            this.parent = parentType;
        }

        @Override
        public boolean matches(Class<?> type) {
            return type != null && parent.isAssignableFrom(type);
        }

        @Override
        public String toString() {
            return "派生于：" + parent.getSimpleName();
        }
    }

    public static class AnnotatedWith implements Test {
        private Class<? extends Annotation> annotation;

        public AnnotatedWith(Class<? extends Annotation> annotation) {
            this.annotation = annotation;
        }

        @Override
        public boolean matches(Class<?> type) {
            return type != null && type.isAnnotationPresent(annotation);
        }

        @Override
        public String toString() {
            return "被标注注解 @" + annotation.getSimpleName();
        }
    }

}

